Modules are a bunch of functions regrouped in a single file, under a single name. Additionally, all functions in Erlang must be defined in modules. You have already used modules, perhaps without realizing it. The BIFs mentioned in the previous chapter, like hd or tl, actually belong to the erlang module, as well as all of the arithmetic, logic and Boolean operators. BIFs from the erlang module differ from other functions as they are automatically imported when you use Erlang. Every other function defined in a module you will ever use needs to be called with the form Module:Function(Arguments).
Logically, you should put functions about similar things inside a single module. Common operations on lists are kept in the lists module, while functions to do input and output (such as writing to the terminal or in a file) are regrouped in the io module. One of the only modules you will encounter which doesn't respect that pattern is the aforementioned erlang module that has functions which do math, conversions, deal with multiprocessing, fiddle with the virtual machine's settings, etc. They have no point in common except being built-in functions. You should avoid creating modules like erlang and instead focus on clean logical separations.
Module Declaration
When writing a module, you can declare two kinds of things: functions and attributes. Attributes are metadata describing the module itself such as its name, the functions that should be visible to the outside world, the author of the code, and so on. This kind of metadata is useful because it gives hints to the compiler on how it should do its job, and also because it lets people retrieve useful information from compiled code without having to consult the source.
There is a large variety of module attributes currently used in Erlang code across the world; as a matter of fact, you can even declare your own attributes for whatever you please. There are some pre-defined attributes that will appear more frequently than others in your code. All module attributes follow the form -Name(Attribute).. Only one of them is necessary for your module to be compilable:
-module(Name).
This is always the first attribute (and statement) of a file, and for good reason: it's the name of the current module, where Name is an atom. This is the name you'll use to call functions from other modules. The calls are made with the M:F(A) form, where M is the module name, F the function, and A the arguments.
-module(operation).
This line of text is a valid module. Really! Of course, it's useless without functions. Let's first decide what functions will be exported from our 'operation' module. To do this, we will use another attribute:
-export([Function1/Arity, Function2/Arity, ..., FunctionN/Arity]).
This is used to define what functions of a module can be called by the outside world. It takes a list of functions with their respective arity. The rarity of a function is an integer representing how many arguments can be passed to the function. This is critical information because different functions defined within a module can share the same name if and only if they have a different arity. The functions add(X, Y) and add(X, Y, Z) would thus be considered different and written in the form add/2 and add/3 respectively.
The syntax of a function follows the form Name(Args) -> Body., where Name has to be an atom and Body can be one or more Erlang expressions separated by commas. The function is ended with a period. Note that Erlang doesn't use the 'return' keyword. 'Return' is useless! Instead, the last logical expression of a function to be executed will have its value returned to the caller automatically without you having to mention it.
What we see from this function is that comments are single-line only and begin with a % sign (using %% is purely a question of style.)
Our operation module will first export a useful function named 'add', which will take two arguments. The following -export attribute can be added after the module declaration:
%% @author localhost
%% @doc @todo Add description to functions.
-module(operation).
%% ====================================================================
%% API functions
%% ====================================================================
-export([add/2,fact/1]).
%% ====================================================================
%% Internal functions
%% ====================================================================
%% Find Factorial
fact(0) -> 1;
fact(N) when N>0 -> N*fact(N-1).
%% Add two numbers
add(A,B) ->
A + B.
How to Import functions from a module to another module:-
-import(Module, [Function1/Arity, ..., FunctionN/Arity]).
Importing a function is not much more than a shortcut for programmers when writing their code. Erlang programmers are often discouraged from using the -import attribute as some people find it reduces the readability of code. In the case of io:format/2, the function io_lib:format/2 also exists. Finding which one is used means going to the top of the file to see from which module it was imported. Consequently, leaving the module name in is considered good practice. Usually, the only functions you'll see imported come from the lists module: its functions are used with a higher frequency than those from most other modules.
We are done with the "operation" module. You can save the file under the name operation.erl. The file name should be the module name as defined in the -module attribute, followed by '.erl', which is the standard Erlang source extension.
Compiling the code:-
Erlang code is compiled to bytecode in order to be used by the virtual machine. You can call the compiler from many places: $ erlc flags file.erl when in the command line, compile:file(FileName) when in the shell or in a module, c() when in the shell, etc.
By default, the shell will only look for files in the same directory it was started in and the standard library: cd/1 is a function defined exclusively for the Erlang shell, telling it to change the directory to a new one so it's less annoying to browse for our files. Windows users should remember to use forward slashes. When this is done, do the following:
3> c(operation).
{ok,operation}
If you have another message, make sure the file is named correctly, that you are in the right directory and that you've made no mistake in your module. Once you successfully compile code, you'll notice that an operation.beam file was added next to operation.erl in your directory. This is the compiled module.
There are a whole lot of compilation flags existing to get more control over how a module is compiled. The most common flags are:
-debug_info
Erlang tools such as debuggers, code coverage, and static analysis tools will use the debug information of a module in order to do their work.
-{outdir,Dir}
By default, the Erlang compiler will create the 'beam' files in the current directory. This will let you choose where to put the compiled file.
-export_all
Will ignore the -export module attribute and will instead export all functions defined. This is mainly useful when testing and developing new code, but should not be used in production.
-{d,Macro} or {d,Macro,Value}
Defines a macro to be used in the module, where Macro is an atom. This is more frequently used when dealing when unit-testing, ensuring that a module will only have its testing functions created and exported when they are explicitly wanted. By default, Value is 'true' if it's not defined as the third element of the tuple.
To compile our operation module with some flags, we could do one of the following:
7> compile:file(operation, [debug_info, export_all]).
{ok,operation}
8> c(operation, [debug_info, export_all]).
{ok,operation}
Leave Comment